﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Reflection;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Net;

namespace Eggplant.Web.Rest.Handlers
{
	public abstract class UriTemplateHandler : AuthenticatingHandler
	{
		public static Dictionary<Type, string> TypeExpressions;
		static Regex FindParametersRegex = new Regex(@"\{([A-Za-z_][A-Za-z_0-9]*)\}");

		static UriTemplateHandler()
		{
			TypeExpressions = new Dictionary<Type, string>();
			TypeExpressions[typeof(string)] = @"[^\/\?\&]+";
			TypeExpressions[typeof(int)] = "[1-9][0-9]*";
			TypeExpressions[typeof(DateTime)] = @"(?<yyyy>[0-9]{4})-(?<mm>[0-9]{2})-(?<dd>[0-9]{2})(T(?<hh>[0-9]{2}):(?<mm>[0-9]{2}):(?<ss>[0-9]{2}))?";
		}

		protected sealed override void ProcessRequest()
		{
			MethodInfo[] methods = this.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
			MethodInfo foundMethod = null;
			UriMappingAttribute foundAttribute = null;
			Match foundMatch = null;
			foreach (MethodInfo method in methods)
			{
				// Get only method with the 'UriMapping' attribute
				UriMappingAttribute attr = (UriMappingAttribute)Attribute.GetCustomAttribute(method, typeof(UriMappingAttribute));
				if (attr == null)
					continue;

				if (attr.Regex == null)
				{
					// Assign the regex to the attribute for later use
					attr.Regex = BuildRegex(method, attr);
				}
				if (!string.IsNullOrEmpty(attr.Regex.ToString()))
				{
					string netopath = this.Context.Request.Url.PathAndQuery.Remove(0, this.Context.Request.ApplicationPath.Length);
					if (!netopath.StartsWith("/"))
						netopath = '/' + netopath;

					Match match = attr.Regex.Match(netopath);
					if (match.Success)
					{
						if (this.Context.Request.HttpMethod == attr.Method)
						{
							foundMethod = method;
							foundMatch = match;
							foundAttribute = attr;
							break;
						}
					}
				}
			}

			if (foundMethod == null || foundMatch == null)
				throw new UriTemplateException("Method not found.", HttpStatusCode.NotFound);

			// Build a list of method arguments
			ParameterInfo[] methodParams = foundMethod.GetParameters();
			object[] methodArgs = new object[methodParams.Length];
			for (int i = 0; i < methodParams.Length; i++)
			{
				ParameterInfo param = methodParams[i];
				object paramVal;
				if (param.Name == foundAttribute.BodyParameter)
				{
					// Handle POST body deserialization
					// TODO: allow deserializing as stream
					paramVal = HttpManager.DeserializeFromInput(this.Context, param.ParameterType);
				}
				else
				{
					// foundMatch.Groups[param.Name] will always find a group because if it were empty - the regex would not have succeeded
					string rawValue = foundMatch.Groups[param.Name].Value;
					paramVal = Convert.ChangeType(rawValue, param.ParameterType);
				}
				methodArgs[i] = paramVal;
			}

			// Run the MOTHERFUCKER
			object val = foundMethod.Invoke(this, methodArgs);

			// return as JSON for now
			HttpManager.SetResponse(this.Context, System.Net.HttpStatusCode.OK, (object)val);
		}

		private static Regex BuildRegex(MethodInfo method, UriMappingAttribute attr)
		{
			Regex targetRegex;

			MatchCollection paramMatches = FindParametersRegex.Matches(attr.Template);
			if (paramMatches.Count < 1)
			{
				targetRegex = new Regex(string.Format(@"^{1}{0}$",
					attr.Template,
					attr.Template.StartsWith("/") ? string.Empty : "/"
					),
				RegexOptions.IgnoreCase);
			}
			else
			{
				// Always start URLs with a leading slash
				string targetRegexPattern = "^" + (attr.Template.StartsWith("/") ? string.Empty : "/");

				ParameterInfo[] parameters = method.GetParameters();
				int lastIndex = 0;

				foreach (Match m in paramMatches)
				{
					targetRegexPattern += Regex.Escape(string.Format("{0}", attr.Template.Substring(lastIndex, m.Index - lastIndex)));
					lastIndex = m.Index + m.Value.Length;

					ParameterInfo foundParam = null;
					foreach (ParameterInfo param in parameters)
					{
						string paramName = m.Groups[1].Value; // get the param name
						if (param.Name == paramName)
						{
							foundParam = param;
							break;
						}
					}

					string paramExpression;

					if (foundParam == null)
					{
						paramExpression = m.Value;
					}
					else
					{
						string typeExpression;
						if (!TypeExpressions.TryGetValue(foundParam.ParameterType, out typeExpression))
							throw new UriTemplateException(foundParam.Name, System.Net.HttpStatusCode.NotFound);
						

						// we found a matching parameter!
						paramExpression = "(?<" + foundParam.Name + ">" + typeExpression + ")";
					}

					targetRegexPattern += paramExpression;
				}

				if (attr.Template.Length > lastIndex)
				{
					targetRegexPattern += Regex.Escape(attr.Template.Substring(lastIndex, attr.Template.Length - lastIndex));
				}

				targetRegex = new Regex(targetRegexPattern+"$");
			}
			return targetRegex;
		}

	}
}